import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static java.util.Collections.emptySet;
import static java.util.Objects.hash;
import static org.apache.commons.lang3.StringUtils.substringBeforeLast;

/**
 * check tcc key 维度的配置里面是否有相同的 key
 */

@Slf4j
public class ModuleConfigDuplicateChecker {

    private final Set<String> duplicateNames = new HashSet<>();
    private final Map<ConfigGroup, Set<String>> mergedNames = new HashMap<>();
    private final Set<String> moduleConfigNames;
    private final Set<String> excludeKeys = new HashSet<>();

    public ModuleConfigDuplicateChecker() {
        this(emptySet());
    }

    public ModuleConfigDuplicateChecker(Set<String> moduleConfigNames) {
        this.moduleConfigNames = moduleConfigNames;
        excludeKeys.add("spring.profiles");
    }

    /**
     * group 逻辑分组名称
     * names 需要校验的名称
     */
    public void assertNotDuplicate(String group, String... names) {
        addName(group, names);
        Set<String> duplicateNames = getDuplicateNames();
        if (!duplicateNames.isEmpty()) {
            log.error("配置存在重复key:" + duplicateNames);
            throw new ConfigDuplicateException(duplicateNames.toString());
        }
    }

    public void addName(String sourceName, String... names) {
        ConfigGroup group = getGroup(sourceName);
        mergedNames.computeIfAbsent(group, key -> new HashSet<>());
        for (String name : names) {
            if (excludeKeys.contains(name)) {
                // not need check
                continue;
            }
            for (Map.Entry<ConfigGroup, Set<String>> g : mergedNames.entrySet()) {
                ConfigGroup key = g.getKey();
                Set<String> value = g.getValue();
                if (key.equals(group)) {
                    value.add(name);
                    key.getSources().add(sourceName);
                    continue;
                }
                boolean exists = value.contains(name);
                if (exists) {
                    duplicateNames.add("key=" + name + ",config=\"" + sourceName + "\":\"" + key.getSources());
                }
            }
        }
    }

    private ConfigGroup getGroup(String sourceName) {
        String groupName = sourceName;
        for (String moduleConfigName : moduleConfigNames) {
            groupName = substringBeforeLast(sourceName, "/" + moduleConfigName);
            if (groupName.length() < sourceName.length()) {
                break;
            }
        }
        ConfigGroup ConfigGroup = new ConfigGroup(groupName);
        ConfigGroup.getSources().add(sourceName);
        return ConfigGroup;
    }

    public Set<String> getDuplicateNames() {
        return duplicateNames;
    }


    @Data
    private static class ConfigGroup {
        private final String name;
        private final Set<String> sources = new HashSet<>();

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            ConfigGroup that = (ConfigGroup) o;
            return name.equals(that.name);
        }

        @Override
        public int hashCode() {
            return hash(name);
        }
    }

    public static class ConfigDuplicateException extends RuntimeException {
        public ConfigDuplicateException(String message) {
            super(message, null, false, false);
        }
    }
}
